/*
 *  cube.c
 *  Ray Tracer
 *
 *  Created by Chirantan Ekbote on 11/9/10.
 *  Copyright 2010 Ohio State University. All rights reserved.
 *
 */

#include "../include/cube.h"
#include "../include/object.h" // Include here because we used forward declaration in header

int cubeHit(const cube* cu, const ray* r, double* t) {
	float temp, pos1, pos2, t1, t2, tMin = INT_MIN, tMax = INT_MAX;
	
	// Check x-axis planes
	pos1 = cu->pos.x - (cu->width * 0.5f);
	pos2 = cu->pos.x + (cu->width * 0.5f);
	if (fabs(r->direction.x) < EPSILON) {
		if (r->origin.x < pos1 || r->origin.x > pos2) {
			return 0;
		}
	} else {
		temp = 1.0f / r->direction.x;
		t1 = (pos1 - r->origin.x) * temp; 
		t2 = (pos2 - r->origin.x) * temp;
		
		if (t1 > t2) {
			temp = t1;
			t1 = t2;
			t2 = temp;
		}
		
		if (t1 > tMin) {
			tMin = t1;
		}
		
		if (t2 < tMax) {
			tMax = t2;
		}
		
		if (tMin > tMax) {
			return 0;
		}
		
		if (tMax < 0) {
			return 0;
		}
	}
	
	// Check y-axis planes
	pos1 = cu->pos.y - (cu->height * 0.5f);
	pos2 = cu->pos.y + (cu->height * 0.5f);
	if (fabs(r->direction.y) < EPSILON) {
		if (r->origin.y < pos1 || r->origin.y > pos2) {
			return 0;
		}
	} else {
		temp = 1.0f / r->direction.y;
		t1 = (pos1 - r->origin.y) * temp;
		t2 = (pos2 - r->origin.y) * temp;
		
		if (t1 > t2) {
			temp = t1;
			t1 = t2;
			t2 = temp;
		}
		
		if (t1 > tMin) {
			tMin = t1;
		}
		
		if (t2 < tMax) {
			tMax = t2;
		}
		
		if (tMin > tMax) {
			return 0;
		}
		
		if (tMax < 0) {
			return 0;
		}
	}

	// Check z-axis planes
	pos1 = cu->pos.z - (cu->depth * 0.5f);
	pos2 = cu->pos.z + (cu->depth * 0.5f);
	if (fabs(r->direction.z) < EPSILON) {
		if (r->origin.z < pos1 || r->origin.z > pos2) {
			return 0;
		}
	} else {
		temp = 1.0f / r->direction.z;
		t1 = (pos1 - r->origin.z) * temp;
		t2 = (pos2 - r->origin.z) * temp;
		
		if (t1 > t2) {
			temp = t1;
			t1 = t2;
			t2 = temp;
		}
		
		if (t1 > tMin) {
			tMin = t1;
		}
		
		if (t2 < tMax) {
			tMax = t2;
		}
		
		if (tMin > tMax) {
			return 0;
		}
		
		if (tMax < 0) {
			return 0;
		}
	}
	
	// If we made it this far we have a hit
	if (tMin < 0) {
		*t = tMax;
	} else {
		*t = tMin;
	}
	return 1;
}

rgb cubeOnHit(const cube* cu, const ray* r, const vec3 hitPos, const struct object* shapes, 
			  const light* lights, int depth, const param* p) {
	rgb c, refractColor, color = rgbScalarMul(cu->ambientColor, AMBIENT_KA);
	vec3 u, v, n, reflectance, normal;
	ray shadowRay;
	
	// variables to keep track of time and index of intersected object
	int index;
	double time;
	
	// Compute normal
	if (fabs(hitPos.x - (cu->pos.x - 0.5f*cu->width)) < EPSILON || 
		fabs(hitPos.x - (cu->pos.x + 0.5f*cu->width)) < EPSILON) {
		// On an x-face
		normal = makeVec3(1.0f, 0.0f, 0.0f);
	} else if (fabs(hitPos.y - (cu->pos.y - 0.5f*cu->height)) < EPSILON || 
			   fabs(hitPos.y - (cu->pos.y + 0.5f*cu->height)) < EPSILON) {
		// On a y-face
		normal = makeVec3(0.0f, 1.0f, 0.0f);
	} else {
		// Must be on a z-face
		normal = makeVec3(0.0f, 0.0f, 1.0f);
	}

	// Loop through lights and add in color
	int i, j;
	if (p->shadows == 2) {	// Soft shadows
		for (i = 1; i <= lights[0].len; i++) {
			// Compute u, v, and n vectors
			n = vec3Neg(vec3UnitVector(vec3Sub(lights[i].pos, hitPos)));
			v = makeVec3(n.x, n.y, 1.0f);
			u = vec3UnitVector(vec3Cross(v, n));
			v = vec3UnitVector(vec3Cross(n, u));
			
			// reset temp color
			c = makeRGB(0.0f, 0.0f, 0.0f);
			
			// Cast shadow rays to area light
			for (j = 0; j < NUM_SUPERSAMPLES; j++) {
				// get the shadowray direction and origin
				shadowRay.direction = vec3UnitVector(vec3Sub(vec3Jitter(lights[i].pos, u, v, n, 
																		AREA_LIGHT_RADIUS, 
																		AREA_LIGHT_RADIUS), 
															 hitPos));
				shadowRay.origin = vec3Add(hitPos, vec3Mul(shadowRay.direction, EPSILON));
				
				// check to see if we are in shadow
				if (!castRay(&shadowRay, shapes, &index, &time)) { 
					// Add diffuse color
					c = rgbAdd(c, rgbScalarMul(cu->diffuseColor, 
											   vec3Dot(normal, shadowRay.direction)));
					
					// compute reflectance (r = 2(N.L)N - L)
					reflectance = vec3Sub(vec3Mul(normal, 
												  2.0f * vec3Dot(shadowRay.direction, normal)), 
										  shadowRay.direction);
					
					// Add specular color (s = (V.R)^S * {1, 1, 1})
					c = rgbAdd(c, rgbScalarMul(lights[i].color, 
											   pow(vec3Dot(vec3Neg(r->direction), 
														   reflectance), SHININESS)));
				}
			}
			// Normalize color and add
			color = rgbAdd(rgbScalarDiv(c, NUM_SUPERSAMPLES), color);
		}
	} else if (p->shadows == 1) {	// Hard shadows
		for (i = 1; i <= lights[0].len; i++) {			
			// Compute shadow ray
			shadowRay.direction = vec3UnitVector(vec3Sub(lights[i].pos, hitPos));
			shadowRay.origin = vec3Add(hitPos, vec3Mul(shadowRay.direction, EPSILON));
			
			// check to see if we are in shadow
			if (!castRay(&shadowRay, shapes, &index, &time)) { 
				// Add diffuse color
				color = rgbAdd(color, rgbScalarMul(cu->diffuseColor, 
												   vec3Dot(normal, shadowRay.direction)));
				
				// compute reflectance (r = 2(N.L)N - L)
				reflectance = vec3Sub(vec3Mul(normal, 
											  2.0f * vec3Dot(shadowRay.direction, normal)), 
									  shadowRay.direction);
				
				// Add specular color (s = (V.R)^S * {1, 1, 1})
				color = rgbAdd(color, rgbScalarMul(makeRGB(1.0f, 1.0f, 1.0f), 
												   pow(vec3Dot(vec3Neg(r->direction), 
															   reflectance), SHININESS)));
			}
		}		
	} else {	// No shadows
		for (i = 1; i <= lights[0].len; i++) {			
			// Compute shadow ray
			shadowRay.direction = vec3UnitVector(vec3Sub(lights[i].pos, hitPos));
			shadowRay.origin = vec3Add(hitPos, vec3Mul(shadowRay.direction, EPSILON));
			
			// Ensure that the shadow ray isn't going through the object itself
			if (vec3Dot(shadowRay.direction, normal) > 0) {
				// Add diffuse color
				color = rgbAdd(color, rgbScalarMul(cu->diffuseColor, 
												   vec3Dot(normal, shadowRay.direction)));
				
				// compute reflectance (r = 2(N.L)N - L)
				reflectance = vec3Sub(vec3Mul(normal, 
											  2.0f * vec3Dot(shadowRay.direction, normal)), 
									  shadowRay.direction);
				
				// Add specular color (s = (V.R)^S * {1, 1, 1})
				color = rgbAdd(color, rgbScalarMul(makeRGB(1.0f, 1.0f, 1.0f), 
												   pow(vec3Dot(vec3Neg(r->direction), 
															   reflectance), SHININESS)));
			}
		}
	}
	
	// Calculate reflection  and refraction if necessary
	if (depth + 1 < MAXDEPTH) {
		float NdotV = vec3Dot(normal, r->direction);
		
		ray reflectionRay;
		
		// Compute reflection ray (r = -2(V.N)N + V)
		reflectionRay.direction = vec3Add(vec3Mul(normal, -2.0f*vec3Dot(r->direction, 
																		normal)), 
										  r->direction);
		reflectionRay.direction = vec3UnitVector(reflectionRay.direction);
		reflectionRay.origin = vec3Add(hitPos, vec3Mul(reflectionRay.direction, EPSILON));
		
		// Cast the ray and get color
		if (castRay(&reflectionRay, shapes, &index, &time)) {
			color = rgbAdd(color, rgbScalarMul(objectOnHit(&(shapes[index]), &reflectionRay, 
														   rayPointAt(&reflectionRay, time), 
														   shapes, lights, depth+1, p), 
											   objectShininess(&(shapes[index]))));
		}
		
		// Calculate Fresnel refraction
		if (cu->transparency > 0) {
			ray refractionRay;
			float cos = 0.0f;
			float refractIndex = AIR_REFRACT_INDEX / CU_REFRACT_INDEX;
			
			if (NdotV < 0) { // Going into medium
				// Get direction
				getRefractionRay(normal, r->direction, refractIndex, &refractionRay);
				
				// Get origin
				refractionRay.origin = vec3Add(hitPos, vec3Mul(refractionRay.direction,EPSILON));
				
				// Cast ray
				if (castRay(&refractionRay, shapes, &index, &time)) {
					refractColor = objectOnHit(&(shapes[index]), &refractionRay, 
											   rayPointAt(&refractionRay, time), shapes, lights, 
											   depth+1, p);
				} else {
					refractColor = makeRGB(0.0f, 0.0f, 0.0f);
				}
				
				cos = -NdotV;
			} else {	// Going out of medium
				// Get direction if there is refraction
				if (getRefractionRay(vec3Neg(normal), r->direction, 1.0f / refractIndex, 
									 &refractionRay)) {
					// Get origin
					refractionRay.origin = vec3Add(hitPos, vec3Mul(refractionRay.direction, 
																   EPSILON));
					
					// Cast ray
					if (castRay(&refractionRay, shapes, &index, &time)) {
						
						refractColor = objectOnHit(&(shapes[index]), &refractionRay, 
												   rayPointAt(&refractionRay, time), shapes, 
												   lights, depth+1, p);
					} else {
						refractColor = makeRGB(0.0f, 0.0f, 0.0f);
					}
					
					cos = vec3Dot(refractionRay.direction, normal);
					color = refractColor;
				} else {	// We have total internal refraction
					refractColor = color;
				}
				
			}
			
			// Optimization -  move r0 into sphere struct since it stays the same
			float r0 = pow(refractIndex - 1, 2) / pow(refractIndex + 1, 2);
			float r = r0 + ( (1-r0) * pow(1-cos, 5));
			refractColor = rgbAdd(rgbScalarMul(color, r), rgbScalarMul(refractColor, 1-r));
			color = rgbAdd(rgbScalarMul(color, 1.0f - cu->transparency), 
						   rgbScalarMul(refractColor, cu->transparency));
		}
		
	}
	
	return color;	
}

